﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using Freedom.Common.Collections;
using log4net;

namespace Freedom.WinCommon.Collections
{
    /// <summary>
    /// PagingLoadObjectCollection
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="T1"> </typeparam>
    public abstract class PagingLoadObjectCollection<T, T1> : ObjectFilterCollection<T, T1>, ILoadableObject
        where T : class, ICopiable<T>, IIdentifiable
        where T1: struct, IComparable, IConvertible, IFormattable
    {
        #region Properties

        private bool _isLoading;
        private bool _continueNeeded = true;
        readonly ObjectCollector<T> _loadedItems = new ObjectCollector<T>();
        private int _startIndexExContinue;
        private int _packageSize = 50;
        private bool _needExportData;
        private int _exportCount;

        private readonly ILog _log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="isStart">if set to <c>true</c> [is start].</param>
        public delegate void OnLoadStartStopEventHandler(bool isStart);

        /// <summary>
        /// Async Load Event Handler
        /// </summary>
        public delegate void AsyncLoadEventHandler();

        /// <summary>
        /// Occurs when [on load start stop event].
        /// </summary>
        public event OnLoadStartStopEventHandler OnLoadStartStopEvent;

        /// <summary>
        /// Gets a value indicating whether this instance is loaded.
        /// </summary>
        /// <value><c>true</c> if this instance is loaded; otherwise, <c>false</c>.</value>
        public bool IsLoaded
        {
            get { return !_isLoading; }
        }

        /// <summary>
        /// Gets a value indicating whether [need continue load].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [need continue load]; otherwise, <c>false</c>.
        /// </value>
        public bool NeedContinueLoad
        {
            get { return _continueNeeded; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [need export data].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [need export data]; otherwise, <c>false</c>.
        /// </value>
        public bool NeedExportData
        {
            get { return _needExportData; }
            set
            {
                _needExportData = value;
                if (!_needExportData)
                    return;

                if (_continueNeeded)
                {
                    IAsyncResult result = ((AsyncLoadEventHandler)AsyncLoad).BeginInvoke(null, null);
                    while (result.IsCompleted == false)
                    {
                        Thread.Sleep(100);
                    }
                    Thread.Sleep(1000);
                }

                _exportCount = Items.Count;
            }
        }

        /// <summary>
        /// The export record count
        /// </summary>
        public int ExportCount
        {
            get { return _exportCount; }
        }

        /// <summary>
        /// Gets the size of the package.
        /// </summary>
        /// <value>The size of the package.</value>
        protected virtual int PackageSize
        {
            get { return _packageSize; }
            set { _packageSize = value; }
        }

        private static readonly IEqualityComparer<T> _comparer = new IdentifiableComparer<T>();

        /// <summary>
        /// Gets the comparer.
        /// </summary>
        /// <value>The comparer.</value>
        protected virtual IEqualityComparer<T> Comparer
        {
            get { return _comparer; }
        }

        /// <summary>
        /// Gets the state of the loading.
        /// </summary>
        /// <value>The get loaded items count.</value>
        /// <returns></returns>
        public int? LoadedItemsCount
        {
            get { return _isLoading ? Items.Count : (int?)null; }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Continues the load.
        /// </summary>
        public void ContinueLoad()
        {
            //if database still have historical items, continue load items, otherwise do nothing
            if (_continueNeeded)
            {
                ((MethodInvoker)AsyncLoad).BeginInvoke(null, null);
            }
        }

        /// <summary>
        /// Updates the state.
        /// </summary>
        public void UpdateState()
        {
            if (_loadedItems.IsEmpty) return;

            AddRange(_loadedItems.ExctractAll().Except(Items, Comparer).ToArray());
        }

        #endregion

        #region Load items

        /// <summary>
        /// Loads the items.
        /// </summary>
        /// <param name="fromConstructor">if set to <c>true</c> [from constructor].</param>
        protected override void LoadItems(bool fromConstructor)
        {
            if (_isLoading) return;
            Clear();
            _startIndexExContinue = 0;
            ((MethodInvoker)AsyncLoad).BeginInvoke(null, null);
        }

        /// <summary>
        /// Finds the by id.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <returns></returns>
        protected T FindById(object id)
        {
            int index;
            return Find(e => e.Id.Equals(id), out index);
        }

        /// <summary>
        /// Asyncs the load. Load items from service
        /// </summary>
        private void AsyncLoad()
        {
            try
            {
                _isLoading = true;
                if (OnLoadStartStopEvent != null)
                    OnLoadStartStopEvent.Invoke(true);

                do
                {
                    //load one page items
                    T[] items = ReadPage(_startIndexExContinue, PackageSize);
                    _startIndexExContinue += items.Length;

                    if (items.Length > 0)
                    {
                        _loadedItems.Put(items);
                        _continueNeeded = items.Length >= PackageSize;
                    }
                    else
                    {
                        _continueNeeded = false;
                    }

                } while (_needExportData && _continueNeeded);
            }
            catch (Exception ex)
            {
                _log.Error(ex);
            }
            finally
            {
                _isLoading = false;
                _needExportData = false;
                if (OnLoadStartStopEvent != null)
                    OnLoadStartStopEvent.Invoke(false);
            }
        }

        #endregion

        #region abstract Methods

        /// <summary>
        /// Reads the page.
        /// </summary>
        /// <param name="startIndex">The start index.</param>
        /// <param name="count">The count.</param>
        protected abstract T[] ReadPage(int startIndex, int count);

        #endregion
    }
}
